Skip to content

docs(error-catalog): INVALID_FORMAT names what really checks a value (field type or a format rule) - #19878

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-19848-error-catalog-invalid-format
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-19848-error-catalog-invalid-format

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #19848
Clause-②: no

What changed

content/docs/api/error-catalog.mdx only.

  1. The INVALID_FORMAT entry. Its Fix line told authors to match "the field's format constraint". No write-time check reads a field-level format key, so following that advice changes nothing. The entry now says what actually decides:
    • No route emits the top-level INVALID_FORMAT today. The entry now says so and tells clients to branch on VALIDATION_FAILED + fields[].code. That is the same shape the page's INVALID_REFERENCE entry already uses.
    • The field type. The built-in email / url / phone checks key on type and answer invalid_email / invalid_url / invalid_phone. Date and time parse failures answer invalid_date / invalid_time.
    • A format validation rule (a different key: its regex or its named format email | url | phone | json) answers field-level invalid_format. The link goes to /docs/data-modeling/validation#format-validation, the anchor PR docs(data-modeling): stop crediting field format with validation #19847 uses.
    • Field-level invalid_format is also emitted for a missed declared pattern outside record metadata: a settings value, or a request body a route parses with Zod.
    • The Fix line now names the field type or the format validation rule as the things to change, and says a field-level format key runs no write-time check on any field type.
  2. A bounded in-place fix in the same file (declared here). The VALIDATION_ERROR JSON example showed an email miss as "code": "invalid_format". The Zod mapper answers invalid_email for that miss. See the Acceptance notes.

The wording follows PR #19847 (still open at the time of writing; this PR depends on none of its files) and the spec's format describe: "keyed on type", "a field-level format key is not read", "a format validation rule".

Evidence (all at base 2bbb4623)

Claim Where
Record validator never reads field format: def.format 0 hits, same-file control def.type 7 packages/objectql/src/validation/record-validator.ts
email / url / phone checks key on type, emit invalid_email / invalid_url / invalid_phone record-validator.ts:746-754
date / time parse failures emit invalid_date / invalid_time record-validator.ts:839, :858
A format validation rule (regex or named format) emits field-level invalid_format packages/objectql/src/validation/rule-validator.ts:2776-2790 (check), :2822 (formatViolation)
Settings pattern miss emits field-level invalid_format packages/services/service-settings/src/settings-service.ts:2042
Zod-parsed routes: email to invalid_email, url to invalid_url, other format/regex to invalid_format packages/spec/src/api/zod-issues-to-fields.ts:82-85
Top-level INVALID_FORMAT has no producer: git grep INVALID_FORMAT outside tests and dist hits only the enum member packages/spec/src/api/errors.zod.ts:57, the ADR note and the unpinned baseline scripts/error-status-unpinned-baseline.json:15 ("documented with an HTTP status that NO producer ... declares"); ADR-0114 line 37 records the six field-shaped top-level members as a known wart
Spec contract on the field key packages/spec/src/data/field.zod.ts:1090-1094 (the format describe: "the write-time record validator's built-in email, url and phone checks key on type, never on this key")

Verification (final head 40758ef8)

  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 41 commands. I ran all 41 on 40758ef8: 41 exited 0.
    • Reconciliation: --ran printed 41 derived famil(ies) accounted for — 41 run, 0 NOT-MEASURED (a DERIVED zero — all 41 recorded an exit code and none of them is 3).
    • On the first pass (d56a2a7f), four gates exited 3 (PREREQUISITE NOT MET): check:doc-formula-expressions, check:doc-security-posture, check:skill-examples and check:docs-transcript-drift. The lint, formula and client packages had not been built yet. After those builds all four re-ran with exit 0.
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/api/error-catalog-docs.test.ts (the test that reads this page against the wire face): Test Files 1 passed (1) · Tests 5 passed (5) on 40758ef8.
  • Not measured, and owned by CI: the families dispatch-gates lists outside its derived total, and the path-scheduled Build Docs / Test Core jobs.

Changeset

Docs-only. content/docs/** is not in any package's files[], so this PR publishes nothing and falls under skip-changeset. Per the dispatch, this seat writes no labels.

Acceptance notes

  • Bounded in-place fix (the VALIDATION_ERROR example invalid_format → invalid_email). All four exemption conditions hold:

    • same defect class (a docs line that says format where the real check keys on the email type);
    • a mechanical, pinned form (zod-issues-to-fields.ts:83);
    • the file is this card's claimed file;
    • the same gate family.

    It lies outside the claim's declared "(the INVALID_FORMAT entry)" sub-surface. The claim's file surface needs this entry added.

  • content/docs/ui/forms.mdx:229 (400 VALIDATION_FAILED · "object schema validators fail (required, format, length, …)"): read, not edited. It lists kinds of constraint in the When column and gives no fix, so it does not tell anyone to edit a field format key. It does not carry the same false meaning. Not listed as a defect.

  • Sibling entries on the same page (a finding, not fixed here): VALUE_TOO_LONG and VALUE_TOO_SHORT also have no producer (git grep outside tests/dist: 0 hits each; control 'VALIDATION_FAILED': 70). Both appear in scripts/error-status-unpinned-baseline.json. The page still documents them as live causes. The record validator answers field-level max_length / min_length under VALIDATION_FAILED instead. This is reported to the seat for filing and is out of scope for this card.


Generated by Claude Code

…s format

The INVALID_FORMAT entry told authors to fix the failure by matching "the
field's `format` constraint", but the write-time record validator never reads
a field-level `format` key: its email / url / phone shape checks key on the
field `type`, and a `format` validation rule (a different key) answers
field-level `invalid_format`. No route emits the top-level INVALID_FORMAT at
all, so the entry now says so and points at VALIDATION_FAILED + fields[].code,
the same shape the INVALID_REFERENCE entry already uses.

The VALIDATION_ERROR example's email entry now carries `invalid_email`, the
code the Zod issue mapper actually produces for an email-format miss.

Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv
Co-authored-by: Claude <noreply@anthropic.com>
Outside `autonumber` the key is a display hint and on `autonumber` it is the
record-number pattern, so 'a display hint the server never checks' was only
right for most types; what holds for every type is that no write-time check
reads it.

Claude-Session: https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 40758ef85a4dc1a7b894bba1530f0e90ea90a57b

① Derived judgments

All measured on origin/main 1f89ba0d70 (git show / git grep only). Diff: 1 file, content/docs/api/error-catalog.mdx, +16/-3.

  1. "No route emits the top-level INVALID_FORMAT today" — TRUE. Whole-tree git grep INVALID_FORMAT excluding tests, dist/** and content/docs hits exactly three non-producers: the enum member packages/spec/src/api/errors.zod.ts:57, the ADR note docs/adr/0114-field-level-error-code-catalog.md:37, and scripts/error-status-unpinned-baseline.json:15. No producer in packages/, apps/, examples/ or scripts/.
  2. Field type checks and codes — TRUE. packages/objectql/src/validation/record-validator.ts:746-753: email → invalid_email, url → invalid_url, phone → invalid_phone (fail's first arg is the wire code, :611-617); :835-839 date/datetime → invalid_date; :848-858 time → invalid_time. def.format: 0 reads (control def.type: 7).
  3. format validation rule → field-level invalid_format — TRUE. rule-validator.ts:2770-2790 (checkFormat: regex miss or named-format miss) → :2818-2822 { code: 'invalid_format' }. Closed set email | url | phone | json — TRUE (packages/spec/src/data/validation.zod.ts:214; matchesNamedFormat :2793-2815). Link target ### Format Validation exists (content/docs/data-modeling/validation.mdx:146).
  4. "a declared pattern outside record metadata … also answers field-level invalid_format" — TRUE on the field code: settings packages/services/service-settings/src/settings-service.ts:2038-2046; Zod-parsed bodies packages/spec/src/api/zod-issues-to-fields.ts:82-85. Advisory only: the settings service wraps its fields[] in a SETTINGS_VALIDATION top-level code (settings-service.types.ts:584), not VALIDATION_FAILED; no changed sentence is false, but the Fix line's "branch on VALIDATION_FAILED" should not be over-generalised to settings writes.
  5. "a field-level format key runs no write-time check on any field type" — TRUE and consistent with the spec describe (packages/spec/src/data/field.zod.ts:1090-1094). On autonumber the key is the record-number pattern used to mint the value (generation, not validation); the validator exempts autonumber (:620-623, :972) and reads no def.format.
  6. JSON example invalid_email — TRUE: an email miss is invalid_email from both the Zod mapper (zod-issues-to-fields.ts:83) and the record validator (:747).
  7. "same shape the page's INVALID_REFERENCE entry already uses" — TRUE (error-catalog.mdx:227-232 on main).
  8. Clause-②: no — right: one docs file, no code, no package surface.
  9. The declared bounded in-place fix (the JSON example) is inside the claimed file; the seat amended the claim surface to include it.

② Semver level

None (docs-only). One file under content/docs/**; no .changeset; no package files[] on origin/main includes content/docs. skip-changeset is the right disposition.

③ Boundary flags

  • No governed surface or release-owned tree touched; head Governed Surface Queue Guard = success.
  • content/docs/ui/forms.mdx untouched; its :229 row lists constraint kinds with no fix — no false prescription.
  • Nothing in the new text misleads an AI author; the one advisory is ①.4.
  • Check-runs on head at read time (30): Check Changeset failure (expected, pending the label); Test Core (1/6), (6/6), Build Docs, the four Type Check · lanes and Lint & Repo Gates in progress; every completed check otherwise success or path-skipped.

Implemented-by: claude/issue-19848-error-catalog-invalid-format
Reviewed-by: session_01VDtqoecgES7ScQYGbFVDRv

VERDICT: PASS

Landing still needs, separately from this verdict: every in-progress check green on this head, and the maintainer's skip-changeset label for Check Changeset. Isolated at-tier reviewer, adopted by the domain:devx#1 seat.


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 24, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 15:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 67ebc84 Sep 24, 2026
37 of 38 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-19848-error-catalog-invalid-format branch September 24, 2026 15:56
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…level codes that really arrive (objectstack-ai#20004)

Fixes objectstack-ai#19879
Clause-②: no

## What

`content/docs/api/error-catalog.mdx`, the `VALUE_TOO_LONG` and
`VALUE_TOO_SHORT` entries only. Both entries gave a live cause and a
fix, as if a client could branch on the code. No producer emits either
code. Both entries now say so, following the shape the `INVALID_FORMAT`
entry got in objectstack-ai#19878: the code is reserved, no route emits it today, and
a length miss arrives as a field-level `max_length` / `min_length`
entry. Each entry now says where that entry rides on each path: `400
VALIDATION_FAILED` with `fields[]` for record writes and Zod-parsed
request bodies (top-level on `/data`, under `details` through the
runtime dispatcher), and `400 SETTINGS_VALIDATION` with
`details.fields[]` for a settings write. The fix line names both
envelopes. Both entries stay because the enum still declares the codes.

## Evidence (measured on `origin/main` `e8f163fc`)

- **No producer.** `git grep -nE 'VALUE_TOO_(LONG|SHORT)'` outside tests
hits only the enum members `packages/spec/src/api/errors.zod.ts:58-59`,
the baseline rows `scripts/error-status-unpinned-baseline.json:27-28`,
this page, the generated `content/docs/references/**` pages, and
ADR-0114, which records these members as a known wart. A grep for other
spellings (`VALUE_TOO`, `TOO_LONG`, `TOO_SHORT`) finds only the
unrelated `PASSWORD_TOO_SHORT` in a plugin-auth test. Positive control:
`INVALID_FORMAT` hits `errors.zod.ts:57`.
- **Record writes.**
`packages/objectql/src/validation/record-validator.ts:695-699` sends
`fail('max_length', { maxLength, actual })` and `fail('min_length', {
minLength, actual })` for `BOUNDED_STRING_FIELD_TYPES`.
`buildFieldError` puts that object on the wire as `fields[].constraint`,
and the envelope's top-level code is `VALIDATION_FAILED`
(`VALIDATION_FAILED_CODE`, `:195`).
- **Zod-parsed request bodies.**
`packages/spec/src/api/zod-issues-to-fields.ts:74-81` maps `too_small` /
`too_big` to `min_length` / `max_length` when the value is not a number,
bigint, date, array or set. Numbers and dates map to `min_value` /
`max_value`, and arrays and sets to `min_items` / `max_items`. That is
why the page says "a string". The REST routes that use this send `code:
'VALIDATION_FAILED'` (for example
`packages/rest/src/rest-server.ts:8960-8963`).
- **Settings writes (a different envelope).**
`packages/services/service-settings/src/settings-service.ts:397-400`
returns `max_length` / `min_length` with `constraint { minLength?,
maxLength?, actual }` for a settings value outside its declared length
window. `:2145` pushes it into the errors list, and `:2167` throws
`SettingsValidationError` (`settings-service.types.ts:583-584`, `code =
'SETTINGS_VALIDATION'`).
`packages/services/service-settings/src/settings-routes.ts:215-217`
serves it as `sendError(res, 400, 'SETTINGS_VALIDATION', …, { details: {
namespace, fields } })`, and `packages/types/src/response-envelope.ts`
`sendError` writes that as `{ success: false, error: { code, message,
details } }`. So a settings length miss is top-level
`SETTINGS_VALIDATION` with the entry in `error.details.fields[]`, not
`VALIDATION_FAILED`.
- **Where `VALIDATION_FAILED` puts the list.** On the `/data` routes it
is flat (`packages/rest/src/error-response.ts:1152-1160`,
`mapDataError`: top-level `fields`). Through the runtime dispatcher it
is nested (`packages/runtime/src/dispatcher-plugin.ts:645`,
`validationFailureDetails`: `details.fields`). The page's own
`VALIDATION_FAILED` callout already documents both, so the entries link
to it rather than restating it.
- The field-level spellings are the same on all three paths:
`max_length` / `min_length`. The envelope differs: `VALIDATION_FAILED`
for records and Zod bodies, `SETTINGS_VALIDATION` for settings.

## Not touched

- The wire-count sentence at `:6`, the `CONCURRENT_LIMIT_EXCEEDED` entry
and `scripts/error-status-unpinned-baseline.json` are not changed. Open
PR objectstack-ai#19957 edits them.
- `VALUE_OUT_OF_RANGE` and `MISSING_REQUIRED_FIELD` are not changed.
Both have producers.
- The page's frontmatter and headings are not changed.

## Changeset

None. This is a docs-only change under `content/docs/`, and no published
package's `files[]` changes, so it falls under `skip-changeset`. The PM
seat applies the label.

## Gates

Derived with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` from the merge-base change set (1
path). All 41 derived commands ran on HEAD `37431df1` and exited 0. The
derivation is unchanged from the first head, `ab8b19ea`. They include
`pnpm check:doc-authoring`, `pnpm check:doc-anchors`, `pnpm
check:nul-bytes`, `pnpm check:error-status-conformance`, `pnpm
check:docs-spec-enumerations` and `pnpm --filter @objectstack/spec run
check:docs`. The prerequisite closures (`lint` / `formula` /
`client-react`, which pulls in `spec`) were built under the verify lock
first. Reconciliation with `--ran` and the recorded exit codes: `41
derived, 41 run, 0 NOT-MEASURED, 0 UNRUN` (a derived zero). No package
source changed, so no package tests or typecheck are owed.

## Rework (PM review)

The first head said every length miss arrives in `VALIDATION_FAILED`,
which is wrong for settings writes. The second commit, `37431df1`, names
`SETTINGS_VALIDATION` + `details.fields[]` for that path in both the
Cause and the Fix lines. The `#validation_failed` link resolves:
`check:doc-anchors` passes, with 377 fragment links resolved.

## Acceptance notes

- The page's intro counts "52 error codes reachable on the wire", but
the page carries 53 code headings, and several of them are reserved
codes with no emitter (`INVALID_FORMAT`, `INVALID_REFERENCE`, and now
these two). Whether that count should include reserved codes is a
question for the line PR objectstack-ai#19957 already edits. It is not changed here.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant